#include <iostream>
#include <exception>
#include <gflags/gflags.h>
#include "OpenCVImage.h"
#include "AppliedWarp.h"
#include "Warper.h"
#include "NearestInterpolator.h"
#include "BilinearInterpolator.h"

using namespace std;

const int SUCCESS = 0;
const int ERROR_INVALID_FLAGS = 1;
const int ERROR_BAD_INPUT = 2;
const int ERROR_BAD_OUTPUT = 3;

// Dimensions of output image.
DEFINE_int32(width, 0, "Output width");
DEFINE_int32(height, 0, "Output height");

// Input and output files.
DEFINE_string(infile, "", "Input file");
DEFINE_string(outfile, "", "Output file");

// Returns true if the flag is not empty.
bool validateNonEmpty(const char *flagname, const string& value) {
  return (value.length() != 0);
}

// Returns true if the flag is greater than zero.
bool validatePositive(const char *flagname, int value) {
  return (value > 0);
}

// Parses the command-line flags.
void parseFlags(int argc, char **argv) {
  // Set usage message.
  const string USAGE("Warps a 360 degree panorama into a planet shape.");
  google::SetUsageMessage(USAGE);

  // Ensure output widths and heights are positive.
  google::RegisterFlagValidator(&FLAGS_width, validatePositive);
  google::RegisterFlagValidator(&FLAGS_height, validatePositive);

  // Ensure input and output files are set.
  google::RegisterFlagValidator(&FLAGS_infile, validateNonEmpty);
  google::RegisterFlagValidator(&FLAGS_outfile, validateNonEmpty);

  google::ParseCommandLineFlags(&argc, &argv, true);
}

int main(int argc, char **argv) {
  parseFlags(argc, argv);

  // Open image file.
  OpenCVImage input;
  if (input.load(FLAGS_infile) != 0) {
    cerr << "Could not load input image" << endl;
    return ERROR_BAD_INPUT;
  }

  // Change warp settings according to flags.
  Warp warp;
  warp.setScaleToSize(FLAGS_width, FLAGS_height);
  warp.setCrop();
  // Apply the warp to the input image.
  AppliedWarp applied_warp(warp, input);

  // Use bilinear interpolation. Repeat horizontally but not vertically.
  BilinearInterpolator interpolator(TiledInterpolator::LOOP,
                                    TiledInterpolator::HOLD);
  // Create output image.
  OpenCVImage output;
  // Execute the warp.
  Warper warper(applied_warp, interpolator, &output);
  warper.warp();

  // Save the result.
  if (output.save(FLAGS_outfile) != 0) {
    cerr << "Could not save output image" << endl;
    return ERROR_BAD_OUTPUT;
  }

  return SUCCESS;
}

